/*
UP3: Command Shell

The system should have a command shell that can do at least the following:

    * Start processes. For example, it shall be possible to start another interpreter.
    * Change priority of processes.
    * Obtain information about present processes.
    * Terminate processes.
    * Ouput to the Malta LCD display
    * It should be possible to have more than one shell running simultaneously.
    * The command shell should not be special-treated by the operating system in any way.
    * The command shell has to handle "backspace" functionality when entering commands.
    * The command shell should not use CPU time when waiting for input.
*/

#include "shell.h"
#include "kprint.h" //TODO remove, for debugging only

/*commands have a fixed structure to make this simpler:
  cmdName param1 param2 param3 ... paramN
  SPACE (Ascii 32) serves as separator between them
*/

/*
Remark: This is ugly and hardcoded but I don't want to steal code from a real shell here because that would be far too sophisticated.
*/
int recg_cmd(char* cmd){

  if(!strcmp(EXIT, cmd))
    return CMD_EXIT_SHELL;

  if(!strcmp(START, cmd))
    return CMD_START_PROC;
  
  if(!strcmp(CHPRIO, cmd))
    return CMD_CHANGE_PRIORITY;

  if(!strcmp(PINFO, cmd))
    return CMD_PINFO;

  if(!strcmp(PS, cmd))
    return CMD_PS;

  if(!strcmp(KILL, cmd))
    return CMD_TERMINATE;

  if(!strcmp(ECHO, cmd))
    return CMD_ECHO;
  
  if(!strcmp(ECHOLCD, cmd))
    return CMD_ECHO_MALTA;
  
  if(!strcmp(HELP, cmd))
    return CMD_HELP;

  if(!strcmp(BLOCK,cmd))
    return CMD_BLOCK;
  
  if(!strcmp(DELAY,cmd))
    return CMD_DELAY;
  
  if(!strcmp(WAKE_UP,cmd))
    return CMD_WAKE_UP;

  if(!strcmp(WAIT,cmd))
    return CMD_WAIT;

  return CMD_NOT_RECOGNIZED;

}

int split_arguments(char* line, char arg_array[][MAX_CMD_LEN]) {
  if (line == NULL) { //to be on the safe side (there should at least be \0)
    ecrire("L'horreur! Line is empty!"); //it's l'horreur (orginally, le horreur was written), nice try still ;)
    quitter(FAILURE);
  }
  int i = 0, arg_number = 0, start_pos = 0, quotes = 0;
  while (line[i] != '\0' && arg_number < MAX_ARGS) {
    if(line[i] == '\"'){
      if(!quotes){
	i++;
	start_pos = i;
	quotes = 1;
	
      }else{
	strncpy(arg_array[arg_number++], &line[start_pos], i-start_pos+1);
	quotes = 0;
	i++;
	//let's "eat" all the whitespaces before the next word
	while(line[i] != '\0' && (line[i] == '\x20' || line[i] == ' '))
	  i++;
	start_pos = i;
      }
      continue;
    }
    if (!quotes && (line[i] == '\x20' || line[i] == ' ')) {
      if(i)
	strncpy(arg_array[arg_number++], &line[start_pos], i-start_pos+1);
      //let's "eat" all the whitespaces before the next word
      while(line[i] != '\0' && (line[i] == '\x20' || line[i] == ' ')){
	i++;
      }
      start_pos = i;
    }else
      i++;
  }
  //don't forget to copy the last arg (if any and possible)
  if(arg_number < MAX_ARGS && i && (i-start_pos))
    strncpy(arg_array[arg_number++], &line[start_pos], i-start_pos+1);
  return arg_number;
}

/*
These are the functions that execute the specific behavior. Here most changes have to be made to adapt this to PORN-OS
*/

void exit_shell(){
  //This one is simple
  ecrire("Thanks for using the pornosshell\n");
  quitter(SUCCESS);
}


void start(char args[][MAX_CMD_LEN], int num_args){
  // reminder: args[0] is the name of the command
  int iargs[4] = {0,0,0,0}; //arguments that will be passed to the process
  int i, len, pid,status;
  char last, buf[MAX_CMD_LEN], ibuf[15];

  if(num_args < 3){
    ecrire("Usage: start priority progname arg1 ... arg4\n");
    return;
  }

  for(i=0;i<num_args-3;i++){
    //all arguments are numerics. If atoi cannot convert into an int, it returns 0
    iargs[i] = atoi(args[i+3]);
  }
  //let's start the process
  
  pid = fourchette(atoi(args[1]), args[2], iargs);
  switch(pid){
  case OUT_OF_MEMORY:
    ecrire("start: not enough memory for a new process. Try killing other processes.\n");
    break;
  case UNKNOWN_PROGRAM:
    strncpy(buf, "start:  program ", MAX_CMD_LEN);
    strncat(buf,args[2], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " unknown\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
    break;
  case WRONG_PRIORITY:
    strncpy(buf, "start:  incorrect priority (", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, ")\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
    break;
  default:
      //do we need to wait for it to finish? If the last character of the last argument is &, we don't
    len = strlen(args[num_args-1]);
    last = args[num_args-1][len-1];
    if(last == '&'){
      //we don', let's print it pid
      strncpy(buf, "start: pid = ", MAX_CMD_LEN);
      strncat(buf,itoa(pid,ibuf,15), MAX_CMD_LEN-strlen(buf));
      strncat(buf, "\n", MAX_CMD_LEN-strlen(buf));
      ecrire(buf);
    }else{
      attendre(pid,&status);
      strncpy(buf, "start:  process returned ", MAX_CMD_LEN);
      strncat(buf,(itoa(status,ibuf,15)), MAX_CMD_LEN-strlen(buf));
      strncat(buf, "\n", MAX_CMD_LEN-strlen(buf));
      ecrire(buf);
    }
    break;
  }
  
}

void chprio(char args[][MAX_CMD_LEN], int arg_num){
  char buf[MAX_CMD_LEN];
  if(arg_num < 3){
    ecrire("Usage: chprio pid priority\n");
    return;
  }
  switch(changer_priorite(atoi(args[1]),atoi(args[2]))){
  case WRONG_PRIORITY:
    strncpy(buf, "chprio:  incorrect priority (", MAX_CMD_LEN);
    strncat(buf,args[2], MAX_CMD_LEN-strlen(buf));
    strncat(buf, ")\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
    break;
  case FAILURE:
    strncpy(buf, "chprio:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist or an error occured in the kernel.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
    break;
  default:
    ;
  }

}

int print_info(int pid){
  char *prgm, ibuf[15];
  int parent, state, priority;
  //get information from pid
  parent = obtenir_parent(pid);
  if(parent == FAILURE)
    return FAILURE;
  state = obtenir_etat(pid);
  if(state == FAILURE)
    return FAILURE;
  priority = obtenir_priorite(pid);
  if(priority == FAILURE)
    return FAILURE;
  prgm = obtenir_programme(pid);
  if(prgm == NULL)
    return FAILURE;
  //let's print them
  ecrire(itoa(pid,ibuf,15));
  ecrire("  ");
  ecrire(itoa(parent,ibuf,15));
  ecrire("  ");
  switch(state){
  case RUNNING_STATE:
    ecrire("Run");
    break;
  case READY_STATE:
    ecrire("Rea");
    break;
  case WAITING_IO_UART_STATE:
    ecrire("WIO");
    break;
  case DOING_IO_UART_STATE:
    ecrire("DIO");
    break;
  case BLOCKED_STATE:
    ecrire("Blo");
    break;
  case DELAYED_STATE:
    ecrire("Del");
    break;
  case WAITING_CHILD_TERM_STATE:
    ecrire("WCh");
    break;
  case ZOMBIE_STATE:
    ecrire("Zom");
    break;
  }
  ecrire("  ");
  ecrire(itoa(priority,ibuf,15));
  ecrire("  ");
  ecrire(prgm);
  ecrire("\n");
  liberer(prgm);
  return SUCCESS;
}

void ps(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN], ibuf[15];
  int len,i,pid;
  int* all_pid = obtenir_tous_pid(&len);
  if(all_pid == NULL){
    ecrire("ps: the system has not enough memory to perform this action.\n");
    return;
  }
  ecrire("PID parent state priority program\n");
  for(i=0;i<len;i++){
    pid = all_pid[i];
    if(print_info(pid) == FAILURE){
      strncpy(buf, "ps:  process ", MAX_CMD_LEN);
      strncat(buf,itoa(pid,ibuf,15), MAX_CMD_LEN-strlen(buf));
      strncat(buf, " doesn't exist or system out of memory.\n", MAX_CMD_LEN-strlen(buf));
      ecrire(buf);
    }
  }
  liberer(all_pid);
}

void get_info(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN];
  if(num_args < 2){
    ecrire("Usage: pinfo pid\n");
    return;
  }
  ecrire("PID parent state priority program\n");
  if(print_info(atoi(args[1])) == FAILURE){
    strncpy(buf, "pinfo:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }
}

void kill(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN];
  if(num_args < 3){
    ecrire("Usage: kill pid status\n");
    return;
  }
  switch(tuer(atoi(args[1]),atoi(args[2]))){
  case FAILURE:
    strncpy(buf, "kill:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
    break;
  case OUT_OF_MEMORY:
    ecrire("kill:  not enough memory to notify the supervisor. Process killed anyway.\n");
    break;
  default:
    ;
  }
}

void block_proc(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN];
  if(num_args < 2){
    ecrire("Usage: block pid\n");
    return;
  }
  if(bloquer(atoi(args[1])) == FAILURE){
    strncpy(buf, "block:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist or cannot be blocked.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }
}

void delay_proc(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN];
  if(num_args < 3){
    ecrire("Usage: delay pid time_in_ms\n");
    return;
  }
  if(retarder(atoi(args[1]),atoi(args[2])) == FAILURE){
    strncpy(buf, "delay:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist or cannot be delayed.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }
}

void wake_up_proc(char args[][MAX_CMD_LEN], int num_args){
  char buf[MAX_CMD_LEN];
  if(num_args < 2){
    ecrire("Usage: wake_up pid\n");
    return;
  }
  if(reveiller(atoi(args[1])) == FAILURE){
    strncpy(buf, "wake_up:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist or cannot be waken up.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }
}

void wait_proc(char args[][MAX_CMD_LEN], int num_args){
  int status;
  char buf[MAX_CMD_LEN], ibuf[15];
  if(num_args < 2){
    ecrire("Usage: wait pid\n");
    return;
  }
  if(attendre(atoi(args[1]),&status) == FAILURE){
    strncpy(buf, "wait:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " doesn't exist or is not a child of the shell.\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }else{
    strncpy(buf, "wait:  process ", MAX_CMD_LEN);
    strncat(buf,args[1], MAX_CMD_LEN-strlen(buf));
    strncat(buf, " returned ", MAX_CMD_LEN-strlen(buf));
    strncat(buf,(itoa(status,ibuf,15)), MAX_CMD_LEN-strlen(buf));
    strncat(buf, "\n", MAX_CMD_LEN-strlen(buf));
    ecrire(buf);
  }
}

void echo(char args[][MAX_CMD_LEN], int num_args){
  if(num_args < 2){
    ecrire("Usage: echo text\n");
    return;
  }
  ecrire(args[1]);
  ecrire("\n");
}

void echo_malta(char args[][MAX_CMD_LEN], int num_args){
  if(num_args < 2){
    ecrire("Usage: echo text\n");
    return;
  }
  ecrire_lcd(args[1]);
}

void help(char args[][MAX_CMD_LEN], int num_args){
  char* help;
  if(num_args == 1)
    ecrire("\nPORN-OS Shell Help\nthe following commands are available:\nexit: exit the shell\nstart priority progname [param1 param2 param3]: start a program\nchprio: change the priority of a process\npinfo: get information about another process\nkill: kill a process\necho string: print a string\necholcd: print a string to malta\nblock: block a process\ndelay: delay a process\nwait: wait for a process to terminate\nwake_up: wake a process up\nps: list all the processes in the system.\nhelp progname: displays progname's help\nhelp : display this help\n");
  else{
    help = obtenir_programme_aide(args[1]);
    if(help == NULL){
      ecrire("help: impossible to get program's help. Unknown program or out of memory.\n");
      return;
    }
    ecrire(help);
  }
}

void shell(){
  char line[MAX_CMD_LEN]; //command has to be shorter than one line
  int cmd_code = CMD_NOT_RECOGNIZED;
  char arg_array[MAX_ARGS][MAX_CMD_LEN]; //0: program_name, 1-4: arguments
  do{

    ecrire("$: ");   
    /* Read a command line */
    if(lire(line, MAX_CMD_LEN) == FAILURE){
      ecrire("Shell: impossible to read input from user\n");
      quitter(FAILURE);
    }
    ecrire("\n");
    int arg_num = split_arguments(line, arg_array);

    cmd_code = recg_cmd(arg_array[0]);
    //take appropriate actions
    switch(cmd_code){

    case CMD_NOT_RECOGNIZED:
      ecrire("Shell: unknown command\n");
      break;
    case CMD_EXIT_SHELL:
      exit_shell();
      break;
    case CMD_START_PROC:
      start(arg_array, arg_num);
      break;
    case CMD_CHANGE_PRIORITY:
      chprio(arg_array, arg_num);
      break;
    case CMD_PINFO:
      get_info(arg_array, arg_num);
      break;
    case CMD_WAIT:
      wait_proc(arg_array, arg_num);
      break;
    case CMD_TERMINATE:
      kill(arg_array, arg_num);
      break;
    case CMD_ECHO:
      echo(arg_array, arg_num);
      break;
    case CMD_ECHO_MALTA:
      echo_malta(arg_array, arg_num);
      break;
    case CMD_BLOCK:
      block_proc(arg_array, arg_num);
      break;
    case CMD_DELAY:
      delay_proc(arg_array, arg_num);
      break;
    case CMD_WAKE_UP:
      wake_up_proc(arg_array, arg_num);
      break;
    case CMD_PS:
      ps(arg_array, arg_num);
      break;
    case CMD_HELP:
      help(arg_array, arg_num);
      break;

    }
  }while(cmd_code != CMD_EXIT_SHELL);

  quitter(SUCCESS);

}
